<title>WebRTC Scalable Broadcast using RTCMultiConnection</title>
<h1><a href="https://github.com/muaz-khan/WebRTC-Scalable-Broadcast">WebRTC Scalable Broadcast</a> using <a href="https://github.com/muaz-khan/RTCMultiConnection">RTCMultiConnection</a></h1>
<hr>
This module simply initializes socket.io and configures it in a way that single broadcast can be relayed over unlimited users without any <a href="https://www.webrtc-experiment.com/docs/RTP-usage.html">bandwidth/CPU usage issues</a>. Everything happens peer-to-peer!

<br><br>
Share files with unlimited users using p2p methods! <a href="/share-files.html">share-files.html</a>
<hr>
<input type="text" id="broadcast-id" placeholder="broadcast-id" value="room-xyz">
<select id="broadcast-options">
    <option>Audio+Video</option>
    <option title="Works only in Firefox.">Audio+Screen</option>
    <option>Audio</option>
    <option>Video</option>
    <option title="Screen capturing requries HTTPs. Please run this demo on HTTPs to make sure it can capture your screens.">Screen</option>
</select>
<button id="open-or-join">Open or Join Broadcast</button>
<hr>
<div id="videos-container"></div>
<style>
video {
    width: 30%;
}
button, input, select {
    font-family: Myriad, Arial, Verdana;
    font-weight: normal;
    border-top-left-radius: 3px;
    border-top-right-radius: 3px;
    border-bottom-right-radius: 3px;
    border-bottom-left-radius: 3px;
    padding: 4px 12px;
    text-decoration: none;
    color: rgb(27, 26, 26);
    display: inline-block;
    box-shadow: rgb(255, 255, 255) 1px 1px 0px 0px inset;
    text-shadow: none;
    background: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(0.05, rgb(241, 241, 241)), to(rgb(230, 230, 230)));
    font-size: 20px;
    border: 1px solid red;
    outline:none;
}
button:active, input:active, select:active, button:focus, input:focus, select:focus {
    background: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(5%, rgb(221, 221, 221)), to(rgb(250, 250, 250)));
    border: 1px solid rgb(142, 142, 142);
}
button[disabled], iput[disabled], select[disabled] {
    background: rgb(249, 249, 249);
    border: 1px solid rgb(218, 207, 207);
    color: rgb(197, 189, 189);
}
input, input:focus, input:active {
    background: white;    
}
</style>
<script src="/socket.io/socket.io.js"></script>
<script src="/RTCMultiConnection.js"></script>
<script>
var socket = io.connect();

// using single socket for RTCMultiConnection signaling
var onMessageCallbacks = {};
socket.on('message', function(data) {
    if (data.sender == connection.userid) return;
    if (onMessageCallbacks[data.channel]) {
        onMessageCallbacks[data.channel](data.message);
    };
});

// initializing RTCMultiConnection constructor.
function initRTCMultiConnection(userid) {
    var connection = new RTCMultiConnection();
    connection.body = document.getElementById('videos-container');
    connection.channel = connection.sessionid = connection.userid = userid || connection.userid;
    connection.sdpConstraints.mandatory = {
        OfferToReceiveAudio: false,
        OfferToReceiveVideo: true
    };
    // using socket.io for signaling
    connection.openSignalingChannel = function(config) {
        var channel = config.channel || this.channel;
        onMessageCallbacks[channel] = config.onmessage;
        if (config.onopen) setTimeout(config.onopen, 1000);
        return {
            send: function(message) {
                socket.emit('message', {
                    sender: connection.userid,
                    channel: channel,
                    message: message
                });
            },
            channel: channel
        };
    };
    connection.onMediaError = function(error) {
        alert(JSON.stringify(error));
    };
    return connection;
}

// this RTCMultiConnection object is used to connect with existing users
var connection = initRTCMultiConnection();

connection.getExternalIceServers = false;

connection.onstream = function(event) {
    connection.body.appendChild(event.mediaElement);

    if (connection.isInitiator == false && !connection.broadcastingConnection) {
        // "connection.broadcastingConnection" global-level object is used
        // instead of using a closure object, i.e. "privateConnection"
        // because sometimes out of browser-specific bugs, browser 
        // can emit "onaddstream" event even if remote user didn't attach any stream.
        // such bugs happen often in chrome.
        // "connection.broadcastingConnection" prevents multiple initializations.

        // if current user is broadcast viewer
        // he should create a separate RTCMultiConnection object as well.
        // because node.js server can allot him other viewers for
        // remote-stream-broadcasting.
        connection.broadcastingConnection = initRTCMultiConnection(connection.userid);

        // to fix unexpected chrome/firefox bugs out of sendrecv/sendonly/etc. issues.
        connection.broadcastingConnection.onstream = function() {};

        connection.broadcastingConnection.session = connection.session;
        connection.broadcastingConnection.attachStreams.push(event.stream); // broadcast remote stream
        connection.broadcastingConnection.dontCaptureUserMedia = true;

        // forwarder should always use this!
        connection.broadcastingConnection.sdpConstraints.mandatory = {
            OfferToReceiveVideo: false,
            OfferToReceiveAudio: false
        };

        connection.broadcastingConnection.open({
            dontTransmit: true
        });
    }
};

// ask node.js server to look for a broadcast
// if broadcast is available, simply join it. i.e. "join-broadcaster" event should be emitted.
// if broadcast is absent, simply create it. i.e. "start-broadcasting" event should be fired.
document.getElementById('open-or-join').onclick = function() {
    var broadcastid = document.getElementById('broadcast-id').value;
    if (broadcastid.replace(/^\s+|\s+$/g, '').length <= 0) {
        alert('Please enter broadcast-id');
        document.getElementById('broadcast-id').focus();
        return;
    }

    this.disabled = true;

    connection.session = {
        video: document.getElementById('broadcast-options').value.indexOf('Video') !== -1,
        screen: document.getElementById('broadcast-options').value.indexOf('Screen') !== -1,
        audio: document.getElementById('broadcast-options').value.indexOf('Audio') !== -1,
        oneway: true
    };

    socket.emit('join-broadcast', {
        broadcastid: broadcastid,
        userid: connection.userid,
        typeOfStreams: connection.session
    });
};

// this event is emitted when a broadcast is already created.
socket.on('join-broadcaster', function(broadcaster, typeOfStreams) {
    connection.session = typeOfStreams;
    connection.channel = connection.sessionid = broadcaster.userid;

    connection.sdpConstraints.mandatory = {
        OfferToReceiveVideo: !!connection.session.video,
        OfferToReceiveAudio: !!connection.session.audio
    };

    connection.join({
        sessionid: broadcaster.userid,
        userid: broadcaster.userid,
        extra: {},
        session: connection.session
    });
});

// this event is emitted when a broadcast is absent.
socket.on('start-broadcasting', function(typeOfStreams) {
    // host i.e. sender should always use this!
    connection.sdpConstraints.mandatory = {
        OfferToReceiveVideo: false,
        OfferToReceiveAudio: false
    };
    connection.session = typeOfStreams;
    connection.open({
        dontTransmit: true
    });

    if (connection.broadcastingConnection) {
        // if new person is given the initiation/host/moderation control
        connection.broadcastingConnection.close();
        connection.broadcastingConnection = null;
    }
});

window.onbeforeunload = function() {
    // Firefox is weird!
    document.getElementById('open-or-join').disabled = false;
};
</script>
